package cn.springsoter.core.s3.template;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.web.multipart.MultipartFile;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.CreateBucketRequest;
import com.amazonaws.services.s3.model.DeleteObjectsRequest;
import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion;
import com.amazonaws.services.s3.model.DeleteObjectsResult;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.amazonaws.util.IOUtils;

import cn.springsoter.core.oss.model.OssFile;
import cn.springsoter.core.oss.model.SpringSoterFile;
import cn.springsoter.core.oss.props.OssProperties;
import cn.springsoter.core.oss.rule.IOssRule;
import cn.springsoter.core.oss.template.OssTemplate;
import cn.springsoter.core.s3.enums.PolicyTypeEnum;
import cn.springsoter.core.tool.utils.FileUtil;
import cn.springsoter.core.tool.utils.StringPool;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * S3Template
 *
 * @author Kevin
 */
@Slf4j
@AllArgsConstructor
public class S3Template implements OssTemplate {

	/**
	 * AmazonS3客户端
	 */
	private AmazonS3 client;

	/**
	 * 存储桶命名规则
	 */
	private IOssRule ossRule;

	/**
	 * 配置类
	 */
	private OssProperties ossProperties;

	@Override
	public void makeBucket(String bucketName) {
		if (!client.doesBucketExistV2(getBucketName(bucketName))) {
			String region = StringUtils.defaultString(ossProperties.getRegion(), Regions.DEFAULT_REGION.getName());
			CreateBucketRequest bucketRequest = new CreateBucketRequest(bucketName, region);
			client.createBucket(bucketRequest);
			client.setBucketPolicy(getBucketName(bucketName), getPolicyType(getBucketName(bucketName), PolicyTypeEnum.READ));
		}
	}

	public Bucket getBucket() {
		return getBucket(getBucketName());
	}

	public Bucket getBucket(String bucketName) {
		Optional<Bucket> bucketOptional = client.listBuckets().stream().filter(bucket -> bucket.getName().equals(getBucketName(bucketName))).findFirst();
		return bucketOptional.orElse(null);
	}
	
	public List<Bucket> listBuckets() {
		return client.listBuckets();
	}

	@Override
	public void removeBucket(String bucketName) {
		client.deleteBucket(getBucketName(bucketName));
	}

	@Override
	public boolean bucketExists(String bucketName) {
		return client.doesBucketExistV2(getBucketName(bucketName));
	}

	@Override
	public void copyFile(String bucketName, String fileName, String destBucketName) {
		copyFile(bucketName, fileName, destBucketName, fileName);
	}

	@Override
	public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
		makeBucket(destBucketName);
		client.copyObject(getBucketName(bucketName), fileName, getBucketName(destBucketName), destFileName);
	}

	@Override
	public OssFile statFile(String fileName) {
		return statFile(getBucketName(), fileName);
	}

	@Override
	public OssFile statFile(String bucketName, String fileName) {
		S3Object stat = client.getObject(new GetObjectRequest(getBucketName(bucketName), fileName));
		OssFile ossFile = new OssFile();
		ossFile.setName(fileName);
		ossFile.setLink(fileLink(bucketName, ossFile.getName()));
		ossFile.setMd5(stat.getObjectMetadata().getETag());//etag相当于文件md5，仅适用于单文件，不适用于分段上传的文件
		ossFile.setLength(stat.getObjectMetadata().getContentLength());
		ossFile.setPutTime(stat.getObjectMetadata().getLastModified());
		ossFile.setContentType(stat.getObjectMetadata().getContentType());
		return ossFile;
	}

	@Override
	public String filePath(String fileName) {
		return getBucketName().concat(StringPool.SLASH).concat(fileName);
	}

	@Override
	public String filePath(String bucketName, String fileName) {
		return getBucketName(bucketName).concat(StringPool.SLASH).concat(fileName);
	}

	@Override
	public String fileLink(String fileName) {
		return ossProperties.getAccessUrl().concat(StringPool.SLASH).concat(getBucketName()).concat(StringPool.SLASH).concat(fileName);
	}

	@Override
	public String fileLink(String bucketName, String fileName) {
		return ossProperties.getAccessUrl().concat(StringPool.SLASH).concat(getBucketName(bucketName)).concat(StringPool.SLASH).concat(fileName);
	}

	@Override
	public SpringSoterFile putFile(MultipartFile file) throws IOException {
		return putFile(getBucketName(), file.getOriginalFilename(), file);
	}

	@Override
	public SpringSoterFile putFile(String fileName, MultipartFile file) throws IOException {
		return putFile(getBucketName(), fileName, file);
	}

	@Override
	public SpringSoterFile putFile(String bucketName, String fileName, MultipartFile file) throws IOException {
		return putFile(bucketName, fileName, file.getInputStream());
	}

	@Override
	public SpringSoterFile putFile(String fileName, InputStream stream) throws IOException {
		return putFile(getBucketName(), fileName, stream);
	}

	@Override
	public SpringSoterFile putFile(String bucketName, String fileName, InputStream stream) throws IOException {
		return putObjectByCopyTempFile(bucketName, fileName, stream);
//		return putObject(bucketName, fileName, stream);
	}

	/**
	 * 文件下载（浏览器直接下载）【指定桶】
	 * 
	 * @param bucketName	指定桶名
	 * @param fileName		下载的文件名
	 * @return
	 */
	public byte[] download(String bucketName, String fileName) {
		try {
			S3Object obj = client.getObject(new GetObjectRequest(bucketName, fileName));
			S3ObjectInputStream s3is = obj.getObjectContent();
			
			byte[] content = IOUtils.toByteArray(s3is);
            obj.close();
            return content;
		} catch (AmazonServiceException e) {
			log.error(ExceptionUtils.getStackTrace(e));
		} catch (FileNotFoundException e) {
			log.error(ExceptionUtils.getStackTrace(e));
		} catch (IOException e) {
			log.error(ExceptionUtils.getStackTrace(e));
		}
		return null;
	}
	
	/**
	 * 文件下载（浏览器直接下载）【使用默认桶：springsoter】
	 * 
	 * @param fileName	下载的文件名
	 */
	public byte[] download(String fileName) {
		return download(getBucketName(), fileName);
	}
	
	/**
	 * 上传文件到s3-本地生成临时文件方式
	 * 优点：避免OOM
	 * 
	 * @param bucketName
	 * @param fileName
	 * @param stream
	 * @return
	 * @throws IOException
	 */
	private SpringSoterFile putObjectByCopyTempFile(String bucketName, String fileName, InputStream stream)
			throws IOException {
		makeBucket(bucketName);
		fileName = getFileName(fileName);
		
		File tempFile = File.createTempFile(FileUtil.getPrefix(fileName), StringPool.DOT + FileUtil.getSuffix(fileName));
		try {
		    FileUtils.copyInputStreamToFile(stream, tempFile);    
		    PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, fileName, tempFile);
		    PutObjectResult putObjectResult = client.putObject(putObjectRequest);
		    
		    log.info("===PutObjectResult===>objectkey={}===>getContentMd5={},getETag={},getMetadata.getContentLength={},getMetadata.getContentType={}", 
		    		fileName,
		    		putObjectResult.getContentMd5(), 
		    		putObjectResult.getETag(), 
		    		putObjectResult.getMetadata().getContentLength(),
		    		putObjectResult.getMetadata().getContentType());
		    
		    SpringSoterFile soterFile = new SpringSoterFile();
			soterFile.setName(fileName);
			soterFile.setLink(fileLink(bucketName, fileName));
			return soterFile;
		} finally {
			//删掉临时文件
		    if(tempFile.exists()) {
		        tempFile.delete();
		    }
		}
	}

	@Override
	public void removeFile(String fileName) {
		removeFile(getBucketName(), fileName);
	}

	@Override
	public void removeFile(String bucketName, String fileName) {
		client.deleteObject(getBucketName(bucketName), fileName);
	}

	@Override
	public void removeFiles(List<String> fileNames) {
		removeFiles(getBucketName(), fileNames);

	}

	@Override
	public void removeFiles(String bucketName, List<String> fileNames) {
		List<KeyVersion> keyVersions = new ArrayList<KeyVersion>(fileNames.size());
        for (String key : fileNames) {
            keyVersions.add(new KeyVersion(key));
        }
        DeleteObjectsRequest delReq = new DeleteObjectsRequest(getBucketName(bucketName))
        		.withKeys(keyVersions)
        		.withQuiet(false);
        DeleteObjectsResult delObjRes = client.deleteObjects(delReq);
        int successfulDeletes = delObjRes.getDeletedObjects().size();
        log.info("===>{} objects successfully deleted.", successfulDeletes);
	}
	
	/**
	 * 上传文件到s3-传统方式
	 * 缺点：有OOM风险
	 * 
	 * @param bucketName
	 * @param fileName
	 * @param stream
	 * @param metaData
	 * @return
	 * @throws IOException
	 */
	private SpringSoterFile putObject(String bucketName, String fileName, InputStream stream) throws IOException {
		makeBucket(bucketName);
		fileName = getFileName(fileName);
		
		// 使用IOUtils.toByteArray读取数据时，这会消耗InputStream。当AWS API尝试读取它时，它的长度为零
		byte[] bytes = IOUtils.toByteArray(stream);
		long fileSize = bytes.length;
		// 将内容读入字节数组并提供将该数组包装到API的InputStream
		ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
		ObjectMetadata metaData = new ObjectMetadata();
		metaData.setContentLength(fileSize);
		metaData.setCacheControl("no-cache");
		
		client.putObject(getBucketName(bucketName), fileName, byteArrayInputStream, metaData);
		SpringSoterFile soterFile = new SpringSoterFile();
		soterFile.setName(fileName);
		soterFile.setLink(fileLink(bucketName, fileName));
		return soterFile;
	}

	/**
	 * 获取默认桶名称
	 *
	 * @return String
	 */
	private String getBucketName() {
		return getBucketName(ossProperties.getBucketName());
	}

	/**
	 * 根据规则生成存储桶名称规则
	 *
	 * @param bucketName 存储桶名称
	 * @return String
	 */
	private String getBucketName(String bucketName) {
		return ossRule.bucketName(bucketName);
	}

	/**
	 * 根据规则生成文件名称规则
	 *
	 * @param originalFilename 原始文件名
	 * @return string
	 */
	private String getFileName(String originalFilename) {
		return ossRule.fileName(originalFilename);
	}

	/**
	 * 获取存储桶策略
	 *
	 * @param policyType 策略枚举
	 * @return String
	 */
	public String getPolicyType(PolicyTypeEnum policyType) {
		return getPolicyType(getBucketName(), policyType);
	}

	/**
	 * 获取存储桶策略
	 *
	 * @param bucketName 存储桶名称
	 * @param policyType 策略枚举
	 * @return String
	 */
	public String getPolicyType(String bucketName, PolicyTypeEnum policyType) {
		StringBuilder builder = new StringBuilder();
		builder.append("{\n");
		builder.append("    \"Statement\": [\n");
		builder.append("        {\n");
		builder.append("            \"Action\": [\n");
		builder.append("                \"s3:GetBucketLocation\",\n");

		switch (policyType) {
			case WRITE:
				builder.append("                \"s3:ListBucketMultipartUploads\"\n");
				break;
			case READ_WRITE:
				builder.append("                \"s3:ListBucket\",\n");
				builder.append("                \"s3:ListBucketMultipartUploads\"\n");
				break;
			default:
				builder.append("                \"s3:ListBucket\"\n");
				break;
		}

		builder.append("            ],\n");
		builder.append("            \"Effect\": \"Allow\",\n");
		builder.append("            \"Principal\": \"*\",\n");
		builder.append("            \"Resource\": \"arn:aws:s3:::");
		builder.append(bucketName);
		builder.append("\"\n");
		builder.append("        },\n");
		builder.append("        {\n");
		builder.append("            \"Action\": ");

		switch (policyType) {
			case WRITE:
				builder.append("[\n");
				builder.append("                \"s3:AbortMultipartUpload\",\n");
				builder.append("                \"s3:DeleteObject\",\n");
				builder.append("                \"s3:ListMultipartUploadParts\",\n");
				builder.append("                \"s3:PutObject\"\n");
				builder.append("            ],\n");
				break;
			case READ_WRITE:
				builder.append("[\n");
				builder.append("                \"s3:AbortMultipartUpload\",\n");
				builder.append("                \"s3:DeleteObject\",\n");
				builder.append("                \"s3:GetObject\",\n");
				builder.append("                \"s3:ListMultipartUploadParts\",\n");
				builder.append("                \"s3:PutObject\"\n");
				builder.append("            ],\n");
				break;
			default:
				builder.append("\"s3:GetObject\",\n");
				break;
		}

		builder.append("            \"Effect\": \"Allow\",\n");
		builder.append("            \"Principal\": \"*\",\n");
		builder.append("            \"Resource\": \"arn:aws:s3:::");
		builder.append(bucketName);
		builder.append("/*\"\n");
		builder.append("        }\n");
		builder.append("    ],\n");
		builder.append("    \"Version\": \"2012-10-17\"\n");
		builder.append("}\n");
		return builder.toString();
	}

}
